FlowViz

GraphManager

function
GraphManager()

Option name Type Description
renderer Renderer The renderer object
layout Layout The layout object

The GraphManager module is responsible for keeping track of the nodes and edges in the visualization.

function GraphManager(flowviz) {
    fv = flowviz;

    this.nodes = [];
    this.edges = [];

    this.tmpNode = null;
    this.tmpEdge = null;

    fv.Renderer.SetNodeListRef(this.nodes);
    fv.Renderer.SetEdgeListRef(this.edges);
}

MoveNode

method
GraphManager.prototype.MoveNode()

Option name Type Description
d FlowNode The node to move
x Number The new x position of the node
y Number The new y position of the node

Moves a node to the provided location and updates any edges connected to the node. This function triggers an update
on the Renderer.

GraphManager.prototype.MoveNode = function(d, x, y) {
    var index;

    var width = $(fv.Selector).width();
    var height = $(fv.Selector).height();

    var nodeW = (d.type.width * d.type.scale);
    var nodeH = (d.type.height * d.type.scale);

    x = x >= 0 ? x : 0;
    y = y >= 0 ? y : 0;

    x = (x + nodeW) <= width ? x : (width - nodeW);
    y = (y + nodeH) <= height ? y : (height - nodeH);

    if((index = $.inArray(d, this.nodes)) >= 0) {
        var moving = this.nodes[index];

        moving.x = x;
        moving.y = y;

        _.forEach(this.edges, function(edge) {
            if(edge.start === moving || edge.end === moving) {
                edge.Update();
            }
        });

        fv.Renderer.Update();
    }
};

AddNode

method
GraphManager.prototype.AddNode()

Option name Type Description
type NodeType Type of a node to be created
x number The initial X position of this node in the visualization.
y number The initial Y position of this node in the visualization.
[update=true] boolean Flag to indicate if the Renderer.Update should be called after adding the node

Creates a new node of specified type and adds it to the main visualization, triggering a Renderer update.

GraphManager.prototype.AddNode = function(type, x, y, update) {
    if(update === undefined) {
        update = true;
    }

    var newNode = new FlowNode(type, x, y);
    this.nodes.push(newNode);

    if(fv.DebugMode) console.log("Node added");
    if(update === true) fv.Renderer.Update();

    fv.emit('node-added', newNode);

    return newNode;
};

AddNodeAutoLayout

method
GraphManager.prototype.AddNodeAutoLayout()

Option name Type Description
type NodeType Type of a node to be created

Creates a new node of specified type and adds it to the main visualization, triggering a Renderer update.
The coordinates for the new node are obtained from the Layout module.

GraphManager.prototype.AddNodeAutoLayout = function(type) {
    var coords = fv.Layout.GetNewNodeCoords(this.nodes);
    return this.AddNode(type, coords[0], coords[1]);
};

RemoveNode

method
GraphManager.prototype.RemoveNode()

Option name Type Description
node FlowNode The node to be removed.

Removes the specified node from the graph and triggers a Renderer update.

GraphManager.prototype.RemoveNode = function(node) {
    // This look convoluted, but all it is is removing edges in place rather than creating a new array
    var index = _.findIndex(this.edges, function(edge) {
        return edge.start === node || edge.end === node;
    });

    while(index >= 0) {
        _.pullAt(this.edges, index);

        index = _.findIndex(this.edges, function(edge) {
            return edge.start === node || edge.end === node;
        });
    }

    _.pull(this.nodes, node);

    fv.Renderer.Update();

    fv.emit('node-removed', node);
    return node;
};

RemoveEdge

method
GraphManager.prototype.RemoveEdge()

Option name Type Description
edge FlowEdge The edge to be removed.

Removes the specified edge from the graph and triggers a Renderer update.

GraphManager.prototype.RemoveEdge = function(edge) {
    _.pull(this.edges, edge);

    if(edge.direction !== FlowEdge.STD) {
        _.forEach(this.edges, function (e) {
            if (e.start === edge.end && e.end === edge.start) {
                e.direction = FlowEdge.STD;
                e.Update();
                fv.Renderer.RedrawEdges();
            }
        });
    }

    fv.Renderer.Update();

    fv.emit('edge-removed', edge);
    return edge;
};

StartConnection

method
GraphManager.prototype.StartConnection()

Option name Type Description
node FlowNode The node from which the edge starts
x number The starting x position of the end of the temp edge
y number The starting y position of the end of the temp edge

Starts the process of creating a new connection between nodes in the graph by creating a temporary edge and node.

GraphManager.prototype.StartConnection = function(node, x, y) {
    console.log("Starting connection");

    this.tmpNode = new FlowNode(NodeType.GetDummy(), x, y);
    this.tmpEdge = new FlowEdge(node, this.tmpNode, FlowEdge.FORWARD);

    fv.Renderer.DrawConnection(this.tmpNode, this.tmpEdge);
};

EndConnection

method
GraphManager.prototype.EndConnection()

Option name Type Description
node FlowNode The end node to connect to

Completes the process of adding a new edge to the graph.

GraphManager.prototype.EndConnection = function(node) {
    // Attempt to add the edge
    this.AddEdge(this.tmpEdge.start, node, FlowEdge.FORWARD, false);

    // Reset the temp node display
    this.tmpNode = null;
    this.tmpEdge = null;
    fv.Renderer.RemoveTempParts();

    // Update the viz
    fv.Renderer.Update();
};

CheckNewEdge

method
GraphManager.prototype.CheckNewEdge()

Option name Type Description
start FlowNode The start node of the new edge
end FlowNode The end node of the new edge
dir number The direction of the new edge

When adding a new edge it is possible that the new edge is drawn over an existing edge. In this case, this function
checks to see if the direction of the edge needs to be updated.

GraphManager.prototype.CheckNewEdge = function(start, end, dir) {
    var dontAddEdge = -1;

    var msg = "";

    if(end === null || start === null) {
        console.error("I can't add an edge if one of the nodes is NULL!");
        return dontAddEdge;
    }

    if(start === end) {
        msg = "Self-loops are not allowed";
        fv.ShowWarning(msg);
        console.error(msg);
        return dontAddEdge;
    }

    if($.inArray(start, this.nodes) < 0) {
        console.error("I can't add an edge if I don't already know the start node!");
        return dontAddEdge;
    }

    if(!$.inArray(end, this.nodes) < 0) {
        console.error("I can't add an edge if I don't already know the end node!");
        return dontAddEdge;
    }

    if(!fv.ConstraintChecker.IsValidEdge(start, end, this.edges)) {
        console.error("This edge does not satisfy constraints!");
        return dontAddEdge;
    }

    dir = FlowEdge.STD;
    var addEdge = true;
    _.forEach(this.edges, function(edge) {
        if(edge.start === start && edge.end === end) {
            msg = "No edges will be added since this edge already exists!";
            fv.ShowWarning(msg);
            console.error(msg);

            addEdge = false;
            return false;
        }
        if(edge.start === end && edge.end === start) {
            edge.direction = FlowEdge.FORWARD;
            edge.Update();

            dir = FlowEdge.BACKWARD;
        }
    });

    if(!addEdge) return dontAddEdge;

    return dir;
};

AddEdge

method
GraphManager.prototype.AddEdge()

Option name Type Description
start FlowNode The start node
end FlowNode The end node
dir number The direction of the edge
[update=true] boolean Indicates if Renderer.Update() should be called after adding the edge

Adds a new edge to the graph. Uses CheckNewEdge to verify if the edge should be added or if an existing edge should
have its tips changed.

GraphManager.prototype.AddEdge = function(start, end, dir, update) {
    if(update === undefined) {
        update = true;
    }

    if(dir === FlowEdge.BACKWARD) {
        var tmp = start;
        start = end;
        end = tmp;
    }

    var edgeDir = this.CheckNewEdge(start, end, dir);

    if(edgeDir >= 0) {

        if(fv.DebugMode) console.log("Edge added");

        var newEdge = new FlowEdge(start, end, edgeDir);
        this.edges.push(newEdge);

        if(update === true) fv.Renderer.Update();

        fv.emit('edge-added', newEdge);

        return newEdge;
    }

    return null;
};

GraphManager.prototype.Save = function(filename) {
    if(filename === undefined) {
        filename = "default.json";
    }

    var jsonEdges = [];
    var jsonNodes = [];
    var jsonTypes = [];

    var types = fv.Config.getAllNodeTypes();

    _.forEach(types, function(type) {
        jsonTypes.push(type.getJSON());
    });

    _.forEach(this.nodes, function(node) {
        jsonNodes.push(node.getJSON());
    });

    _.forEach(this.edges, function(edge) {
        jsonEdges.push(edge.getJSON());
    });

    var url = 'data:text/json;charset=utf8,' + JSON.stringify({
            "filename": filename,
            "types": jsonTypes,
            "nodes": jsonNodes,
            "edges": jsonEdges
        });
    window.open(url, '_blank');
    window.focus();
};

AutoCompleteIncoming

method
GraphManager.prototype.AutoCompleteIncoming() ->boolean

Option name Type Description
[doLayout=true] boolean Indicates if the auto layout should be performed after the creation of new edges.

For every node in the graph, checks if it has exactly one required incoming node, then creates that node and
the corresponding edge.

GraphManager.prototype.AutoCompleteIncoming = function(doLayout) {
    if(doLayout === undefined) {
        doLayout = true;
    }
    var hasNew = false;
    _.forEach(this.nodes, function(n) {
        var neighbors = fv.ConstraintChecker.GetUnambiguousRequiredTypes(n, fv.GraphManager.edges, false);
        _.forEach(neighbors, function(neighbor) {
            var newNode = fv.GraphManager.AddNodeAutoLayout(neighbor);
            fv.GraphManager.AddEdge(newNode, n, FlowEdge.FORWARD);
            hasNew = true;
        });

    });
    if (doLayout) {
        fv.Layout.AutoLayout(this.nodes, this.edges);
        fv.Renderer.Update();
    }
    return hasNew;
};

AutoCompleteOutgoing

method
GraphManager.prototype.AutoCompleteOutgoing() ->boolean

Option name Type Description
[doLayout=true] boolean Indicates if the auto layout should be performed after the creation of new edges.

For every node in the graph, checks if it has exactly one required outgoing node, then creates that node and
the corresponding edge.

GraphManager.prototype.AutoCompleteOutgoing = function(doLayout) {
    if(doLayout === undefined) {
        doLayout = true;
    }
    var hasNew = false;
    _.forEach(this.nodes, function(n) {
        var neighbors = fv.ConstraintChecker.GetUnambiguousRequiredTypes(n, fv.GraphManager.edges, true);
        _.forEach(neighbors, function(neighbor) {
            var newNode = fv.GraphManager.AddNodeAutoLayout(neighbor);
            fv.GraphManager.AddEdge(n, newNode, FlowEdge.FORWARD);
            hasNew = true;
        });

    });
    if (doLayout) {
        fv.Layout.AutoLayout(this.nodes, this.edges);
        fv.Renderer.Update();
    }
    return hasNew;
};

AutoCompleteAll

method
GraphManager.prototype.AutoCompleteAll() ->boolean

Option name Type Description
[doLayout=true] boolean Indicates if the auto layout should be performed after the creation of new edges.

For every node in the graph, checks if it has exactly one required incoming or outgoing node, then creates that node and
the corresponding edge.

GraphManager.prototype.AutoCompleteAll = function(doLayout) {
    var that = this;
    if(doLayout === undefined) {
        doLayout = true;
    }
    var hasNew = this.AutoCompleteIncoming(false);
    hasNew = hasNew || this.AutoCompleteOutgoing(false);
    if (doLayout) {
        fv.Layout.AutoLayout(fv.GraphManager.nodes, fv.GraphManager.edges);
        fv.Renderer.Update();
    }
    return hasNew;
};